﻿using System;
using System.Collections.Generic;
using System.Linq;
using ContactCenter.Core;
using ContactCenter.Core.Common;
using ContactCenter.Core.DomainModel;
using ContactCenter.Persistence.NH;
using ContactCenter.Persistence.NH.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Criterion;
using SharpTestsEx;

namespace ContactCenter.AuditEventListener.Tests
{
    /// <summary>
    /// Integration tests for Audit Event Listener.
    /// </summary>
    [TestClass]
    public class AuditEventListenerFixture
    {
        private static ISessionFactory _sessionFactory;
        private static Configuration _configuration;

        [ClassInitialize]
        public static void Init(TestContext testContext)
        {
            _configuration = TaskExecutor.Execute(new SqliteDbTask(),new DomainMappingTask());
            //_configuration = TaskExecutor.Execute(new MsSqlDbTask(), new DomainMappingTask());
        }

        [TestInitialize]
        public void Setup()
        {
            _sessionFactory = TaskExecutor.Execute(
                                    () => _configuration, 
                                    new AuditListenerTask(),
                                    new AuditListenerMappingTask(),
                                    new SchemaExportTask()
                                    ).BuildSessionFactory();

            LoadTestData();
        }

        private static ISession GetSession()
        {
            return _sessionFactory.OpenSession();
        }

        private static void LoadTestData()
        {
            using (var session = GetSession())
            {
                using (var tx = session.BeginTransaction())
                {
                    for (var i = 1; i < 4; i++)
                    {
                        var cust = Customer.Create("Customer" + i);
                        cust.ChangeCreditCard(new CreditCard("1234-5678-9101", "1121", DateTime.Today.AddYears(1)));
                        cust.ChangeContactInfo(new ContactInfo("123456789", "132456789", "123456789", string.Format("info@customer{0}.it", i)));
                        cust.AddAddress(new Address(cust, "address" + i, "12345", "city" + i, "region" + i));
                        session.Save(cust);
                    }
                    tx.Commit();
                }
            }
        }

        /// <summary>
        /// Reads an Audit Information having the specified property equals to the specified value.
        /// </summary>
        private static IList<AuditInfo> GetAuditInfoHaving(string propertyName, string value)
        {
            IList<AuditInfo> auditInfo;
            using (var session = GetSession())
            {
                using (var tx = session.BeginTransaction())
                {
                    auditInfo = session.CreateCriteria<AuditInfo>()
                        .Add(Restrictions.Eq(propertyName, value))
                        .List<AuditInfo>();
                    tx.Commit();
                }
            }
            return auditInfo;
        }

        public TestContext TestContext { get; set; }

        [TestMethod]
        public void TestDataArePresent()
        {
            using (var session = GetSession())
            {
                var result = session.CreateQuery("from Customer").List<Customer>();
                result.Count.Should().Be.GreaterThan(0);
            }
        }

        [TestMethod]
        public void ModifyingAPropertyShouldCauseAuditing()
        {
            const string OLD_CUSTOMER_NAME = "Customer1";
            const string NEW_CUSTOMER_NAME = "BestCustomer";

            using (var session = GetSession())
            {
                // Gets a customer and modifies a property on it.
                using (var tx = session.BeginTransaction())
                {
                    var customer = session.CreateCriteria<Customer>()
                        .Add(Restrictions.Eq("CustomerName", OLD_CUSTOMER_NAME))
                        .List<Customer>()[0];
                    customer.ChangeCustomerName(NEW_CUSTOMER_NAME);
                    tx.Commit();
                }

            }

            IList<AuditInfo> auditInfo = GetAuditInfoHaving("OldValue", OLD_CUSTOMER_NAME);
            auditInfo[0].NewValue.Should().Be.EqualTo(NEW_CUSTOMER_NAME);
        }

        [TestMethod]
        public void ModifyingComponentPropertyShouldCauseAuditing()
        {
            const string OLD_EMAIL = "info@customer1.it";
            const string NEW_EMAIL = "webmaster@customer1.it";

            using (var session = GetSession())
            {
                // Gets a customer and modifies a component property.
                using (var tx = session.BeginTransaction())
                {
                    var customer = session.CreateCriteria<Customer>()
                        .Add(Restrictions.Eq("ContactInfo.EMail", OLD_EMAIL))
                        .List<Customer>()[0];

                    var newContactInfo = new ContactInfo(customer.ContactInfo.HomePhoneNumber, customer.ContactInfo.WorkPhoneNumber, customer.ContactInfo.MobilePhoneNumber, NEW_EMAIL);
                    customer.ChangeContactInfo(newContactInfo);

                    tx.Commit();
                }
            }

            IList<AuditInfo> auditInfo = GetAuditInfoHaving("OldValue", OLD_EMAIL);
            auditInfo[0].NewValue.Should().Be.EqualTo(NEW_EMAIL);
        }

        [TestMethod]
        public void ModifyingCollectionElementShouldCauseAuditing()
        {
            const string CUSTOMER_NAME = "Customer1";

            using (var session = GetSession())
            {
                // Gets a customer and modifies a addesses collection item on it.
                using (var tx = session.BeginTransaction())
                {
                    var customer = session.CreateCriteria<Customer>()
                        .Add(Restrictions.Eq("CustomerName", CUSTOMER_NAME))
                        .List<Customer>()[0];

                    Address address = customer.Addresses.First(addr => addr.Street == "address1");
                    address.Street = "address1x";
                    address.PostalCode = "12345";
                    address.City = "city1x";
                    address.Region = "region1x";

                    tx.Commit();
                }
            }

            IList<AuditInfo> auditInfo = GetAuditInfoHaving("OldValue", "address1");
            auditInfo[0].NewValue.Should().Be.EqualTo("address1x");
        }

        [TestMethod]
        public void SavingTheSameValueOnPropertyShouldNotCauseAuditing()
        {
            const string CUSTOMER_NAME = "Customer1";

            using (var session = GetSession())
            {
                // Gets a customer and modifies a property value on it, placing the same value.
                using (var tx = session.BeginTransaction())
                {
                    var customer = session.CreateCriteria<Customer>()
                        .Add(Restrictions.Eq("CustomerName", CUSTOMER_NAME))
                        .List<Customer>()
                        .FirstOrDefault();

                    customer.ChangeCustomerName(CUSTOMER_NAME);
                    tx.Commit();
                }
            }

            GetAuditInfoHaving("OldValue", CUSTOMER_NAME).Count.Should().Be.EqualTo(0);
        }

        [TestMethod]
        public void SavingTheSameValuesOnComponentPropertyShouldNotCauseAuditing()
        {
            const string CUSTOMER_NAME = "Customer1";
            string originaCreditCardPublicPart;

            using (var session = GetSession())
            {
                // Gets a customer and modifies a component property on it, placing the same value.
                using (var tx = session.BeginTransaction())
                {
                    var customer = session.CreateCriteria<Customer>()
                        .Add(Restrictions.Eq("CustomerName", CUSTOMER_NAME))
                        .List<Customer>()
                        .FirstOrDefault();

                    CreditCard cc = customer.CreditCard;
                    originaCreditCardPublicPart = cc.PublicPart;
                    customer.ChangeCreditCard(cc);

                    tx.Commit();
                }
            }

            GetAuditInfoHaving("OldValue", originaCreditCardPublicPart).Count.Should().Be.EqualTo(0);
        }

        [TestMethod]
        public void AddingItemToChildCollectionShouldCauseAuditing()
        {
            const string CUSTOMER_NAME = "Customer2";

            using (var session = GetSession())
            {
                using (var tx = session.BeginTransaction())
                {
                    var customer = session.CreateCriteria<Customer>()
                        .Add(Restrictions.Eq("CustomerName", CUSTOMER_NAME))
                        .List<Customer>()
                        .FirstOrDefault();

                    customer.AddAddress(new Address(customer, "address21", "23456", "city21", "region21"));
                    tx.Commit();
                }
            }

            IList<AuditInfo> auditInfo = GetAuditInfoHaving("FieldName", "Addresses");
            auditInfo.Count.Should().Be.EqualTo(1);
            auditInfo[0].Action.Should().Be.EqualTo(AuditActions.Inserted);
        }

        [TestMethod]
        public void RemovingItemToChildCollectionShouldCauseAuditing()
        {
            const string CUSTOMER_NAME = "Customer2";

            using (var session = GetSession())
            {
                using (var tx = session.BeginTransaction())
                {
                    var customer = session.CreateCriteria<Customer>()
                        .Add(Restrictions.Eq("CustomerName", CUSTOMER_NAME))
                        .List<Customer>()
                        .FirstOrDefault();

                    Address address = customer.Addresses.First(addr => addr.Street == "address2");
                    customer.RemoveAddress(address);
                    tx.Commit();
                }
            }

            IList<AuditInfo> auditInfo = GetAuditInfoHaving("FieldName", "Addresses");
            auditInfo.Count.Should().Be.EqualTo(1);
            auditInfo[0].Action.Should().Be.EqualTo(AuditActions.Deleted);
        }

        [TestMethod]
        public void RemovingAndAddingItemsToChildCollectionShouldCauseAuditing()
        {
            const string CUSTOMER_NAME = "Customer2";

            using (var session = GetSession())
            {
                using (var tx = session.BeginTransaction())
                {
                    var customer = session.CreateCriteria<Customer>()
                        .Add(Restrictions.Eq("CustomerName", CUSTOMER_NAME))
                        .List<Customer>()
                        .FirstOrDefault();

                    customer.RemoveAllAddresses();
                    customer.AddAddress(new Address(customer, "address21", "23456", "city21", "region21"));
                    tx.Commit();
                }
            }

            IList<AuditInfo> auditInfo = GetAuditInfoHaving("FieldName", "Addresses");
            auditInfo.Count.Should().Be.EqualTo(2);
        }
    }   
}
